<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="Java面试题（Redis篇）, Angus">
    <meta name="description" content="Java面试Redis题型相关讲解">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="referrer" content="no-referrer-when-downgrade">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    

    <title>Java面试题（Redis篇） | Angus</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery-3.6.0.min.js"></script>

<meta name="generator" content="Hexo 6.1.0">
<style>.github-emoji { position: relative; display: inline-block; width: 1.2em; min-height: 1.2em; overflow: hidden; vertical-align: top; color: transparent; }  .github-emoji > span { position: relative; z-index: 10; }  .github-emoji img, .github-emoji .fancybox { margin: 0 !important; padding: 0 !important; border: none !important; outline: none !important; text-decoration: none !important; user-select: none !important; cursor: auto !important; }  .github-emoji img { height: 1.2em !important; width: 1.2em !important; position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; user-select: none !important; cursor: auto !important; } .github-emoji-fallback { color: inherit; } .github-emoji-fallback img { opacity: 0 !important; }</style>
<link rel="alternate" href="/atom.xml" title="Angus" type="application/atom+xml">
<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>


<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">Angus</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">Angus</div>
        <div class="logo-desc">
            
            never give up!
            
        </div>
    </div>

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://github.com/Monkey-chao/Monkey-chao.github.io.git" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://github.com/Monkey-chao/Monkey-chao.github.io.git" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/0.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">Java面试题（Redis篇）</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/tags/Redis/">
                                <span class="chip bg-color">Redis</span>
                            </a>
                        
                            <a href="/tags/Java/">
                                <span class="chip bg-color">Java</span>
                            </a>
                        
                            <a href="/tags/%E9%9D%A2%E8%AF%95/">
                                <span class="chip bg-color">面试</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/categories/%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/" class="post-category">
                                面试宝典
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2022-08-10
                </div>
                

                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-check fa-fw"></i>更新日期:&nbsp;&nbsp;
                    2022-09-11
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-file-word fa-fw"></i>文章字数:&nbsp;&nbsp;
                    11.2k
                </div>
                

                
                <div class="info-break-policy">
                    <i class="far fa-clock fa-fw"></i>阅读时长:&nbsp;&nbsp;
                    39 分
                </div>
                

                
                    <div id="busuanzi_container_page_pv" class="info-break-policy">
                        <i class="far fa-eye fa-fw"></i>阅读次数:&nbsp;&nbsp;
                        <span id="busuanzi_value_page_pv"></span>
                    </div>
				
            </div>
        </div>
        <hr class="clearfix">

        

        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h1 id="Java面试题（Redis篇）"><a href="#Java面试题（Redis篇）" class="headerlink" title="Java面试题（Redis篇）"></a>Java面试题（Redis篇）</h1><h2 id="一、Redis是什么？简述它的优缺点"><a href="#一、Redis是什么？简述它的优缺点" class="headerlink" title="一、Redis是什么？简述它的优缺点"></a>一、Redis是什么？简述它的优缺点</h2><p>﻿Redis本质上是一个Key-Value类型的内存数据库，很像Memcached，整个数据库加载在内存当中操作，定期通过异步操作把数据库中的数据flush到硬盘上进行保存。</p>
<p>﻿因为是纯内存操作，Redis的性能非常出色，每秒可以处理超过 10万次读写操作，是已知性能最快的Key-Value 数据库。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>读写性能极高， Redis能读的速度是110000次/s，写的速度是81000次/s。</li>
<li>支持数据持久化，支持AOF和RDB两种持久化方式。</li>
<li>支持事务， Redis的所有操作都是原子性的，意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务，即原子性，通过MULTI和EXEC指令包起来。</li>
<li>数据结构丰富，除了支持string类型的value外，还支持hash、set、zset、list等数据结构。</li>
<li>支持主从复制，主机会自动将数据同步到从机，可以进行读写分离。</li>
<li>丰富的特性 – Redis还支持 publish/subscribe， 通知， key 过期等特性。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>数据库容量受到物理内存的限制，不能用作海量数据的高性能读写，因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。</li>
<li>主机宕机，宕机前有部分数据未能及时同步到从机，切换IP后还会引入数据不一致的问题，降低了系统的可用性。</li>
</ul>
<h2 id="一、什么是RDB和AOF"><a href="#一、什么是RDB和AOF" class="headerlink" title="一、什么是RDB和AOF"></a>一、什么是RDB和AOF</h2><p><strong>RDB：</strong>Redis DataBase，在指定的时间间隔内将内存中的数据集快照写⼊磁盘，实际操作过程是fork⼀ 个⼦进程，先将数据集写⼊临时⽂件，写⼊成功后，再替换之前的⽂件，⽤⼆进制压缩存储。 </p>
<p><strong>优点：</strong> </p>
<ol>
<li><p>整个Redis数据库将只包含⼀个⽂件 dump.rdb，⽅便持久化。 </p>
</li>
<li><p>容灾性好，⽅便备份。 </p>
</li>
<li><p>性能最⼤化，fork ⼦进程来完成写操作，让主进程继续处理命令，所以是 IO 最⼤化。使⽤单独⼦进程来进⾏持久化，主进程不会进⾏任何 IO 操作，保证了 redis 的⾼性能 </p>
</li>
<li><p>相对于数据集⼤时，⽐ AOF 的启动效率更⾼。</p>
</li>
</ol>
<p><strong>缺点：</strong> </p>
<ol>
<li><p>数据安全性低。RDB 是间隔⼀段时间进⾏持久化，如果持久化之间 redis 发⽣故障，会发⽣数据丢 失。所以这种⽅式更适合数据要求不严谨的时候) </p>
</li>
<li><p>由于RDB是通过fork⼦进程来协助完成数据持久化⼯作的，因此，如果当数据集较⼤时，可能会导 致整个服务器停⽌服务⼏百毫秒，甚⾄是1秒钟。 AOF：Append Only File，以⽇志的形式记录服务器所处理的每⼀个写、删除操作，查询操作不会记录，以⽂本的⽅式记录，可以打开⽂件看到详细的操作记录</p>
</li>
</ol>
<p><strong>优点：</strong> </p>
<ol>
<li><p>数据安全，Redis中提供了3中同步策略，即每秒同步、每修改同步和不同步。事实上，每秒同步也 是异步完成的，其效率也是⾮常⾼的，所差的是⼀旦系统出现宕机现象，那么这⼀秒钟之内修改的 数据将会丢失。⽽每修改同步，我们可以将其视为同步持久化，即每次发⽣的数据变化都会被⽴即记录到磁盘中。。 </p>
</li>
<li><p>通过 append 模式写⽂件，即使中途服务器宕机也不会破坏已经存在的内容，可以通过 redis-check-aof ⼯具解决数据⼀致性问题。 </p>
</li>
<li><p>AOF 机制的 rewrite 模式。定期对AOF⽂件进⾏重写，以达到压缩的⽬的</p>
</li>
</ol>
<p><strong>缺点：</strong> </p>
<ol>
<li><p>AOF ⽂件⽐ RDB ⽂件⼤，且恢复速度慢。 </p>
</li>
<li><p>数据集⼤的时候，⽐ rdb 启动效率低。 </p>
</li>
<li><p>运⾏效率没有RDB⾼</p>
</li>
</ol>
<p>AOF⽂件⽐RDB更新频率⾼，优先使⽤AOF还原数据，AOF⽐RDB更安全也更⼤，RDB性能⽐AOF好， 如果两个都配了优先加载AOF。</p>
<h2 id="二、Redis的过期键的删除策略"><a href="#二、Redis的过期键的删除策略" class="headerlink" title="二、Redis的过期键的删除策略"></a>二、Redis的过期键的删除策略</h2><p>Redis是key-value数据库，我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当 Redis中缓存的key过期了，Redis如何处理</p>
<ul>
<li><p>惰性过期：只有当访问⼀个key时，才会判断该key是否已过期，过期则清除。该策略可以最⼤化地节省CPU资源，却对内存⾮常不友好。极端情况可能出现⼤量的过期key没有再次被访问，从⽽不会被清除，占⽤⼤量内存。 </p>
</li>
<li><p>定期过期：每隔⼀定的时间，会扫描⼀定数量的数据库的expires字典中⼀定数量的key，并清除其中已过期的key。该策略是⼀个折中⽅案。通过调整定时扫描的时间间隔和每次扫描的限定耗时，可以在不同情况下使得CPU和内存资源达到最优的平衡效果。</p>
</li>
</ul>
<p>(expires字典会保存所有设置了过期时间的key的过期时间数据，其中，key是指向键空间中的某个键的 指针，value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。) </p>
<p>Redis中同时使⽤了惰性过期和定期过期两种过期策略</p>
<h2 id="三、Redis线程模型、单线程快的原因"><a href="#三、Redis线程模型、单线程快的原因" class="headerlink" title="三、Redis线程模型、单线程快的原因"></a>三、Redis线程模型、单线程快的原因</h2><p>Redis基于Reactor模式开发了⽹络事件处理器，这个处理器叫做⽂件事件处理器 file event handler。 这个⽂件事件处理器，它是单线程的，所以 Redis 才叫做单线程的模型，它采⽤IO多路复⽤机制来同时监听多个Socket，根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现⾼性能 的⽹络通信模型，⼜可以跟内部其他单线程的模块进⾏对接，保证了 Redis 内部的线程模型的简单性。 </p>
<p>⽂件事件处理器的结构包含4个部分：多个Socket、IO多路复⽤程序、⽂件事件分派器以及事件处理器（命令请求处理器、命令回复处理器、连接应答处理器等）。多个 Socket 可能并发的产⽣不同的操作，每个操作对应不同的⽂件事件，但是IO多路复⽤程序会监听多个 Socket，会将 Socket 放⼊⼀个队列中排队，每次从队列中取出⼀个 Socket 给事件分派器，事件分派器把 Socket 给对应的事件处理器</p>
<p>然后⼀个 Socket 的事件处理完之后，IO多路复⽤程序才会将队列中的下⼀个 Socket 给事件分派器。 ⽂件事件分派器会根据每个 Socket 当前产⽣的事件，来选择对应的事件处理器来处理。 </p>
<p>单线程快的原因： </p>
<p>1）纯内存操作 </p>
<p>2）核⼼是基于⾮阻塞的IO多路复⽤机制 </p>
<p>3）单线程反⽽避免了多线程的频繁上下⽂切换带来的性能问题 </p>
<h2 id="四、简述Redis事务实现"><a href="#四、简述Redis事务实现" class="headerlink" title="四、简述Redis事务实现"></a>四、简述Redis事务实现</h2><p>1、事务开始 </p>
<p><em>MULTI</em>命令的执⾏，标识着⼀个事务的开始。<em>MULTI</em>命令会将客户端状态的 flags 属性中打开 REDIS_MULTI 标识来完成的。 </p>
<p>2、命令⼊队 </p>
<p>当⼀个客户端切换到事务状态之后，服务器会根据这个客户端发送来的命令来执⾏不同的操作。如果客 户端发送的命令为<em>MULTI</em>、<em>EXEC</em>、<em>WATCH</em>、<em>DISCARD</em>中的⼀个，⽴即执⾏这个命令，否则将命令放 ⼊⼀个事务队列⾥⾯，然后向客户端返回 QUEUED 回复</p>
<ul>
<li>如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中⼀个，那么服务器⽴即执⾏这个命令。</li>
</ul>
<p>如果客户端发送的是四个命令以外的其他命令，那么服务器并不⽴即执⾏这个命令。⾸先检查此命令的格式是否正确，如果不正确，服务器会在客户端状态（redisClient）的 flags 属性关闭 REDIS_MULTI 标识，并且返回错误信息给客户端。如果正确，将这个命令放⼊⼀个事务队列⾥⾯，然后向客户端返回 QUEUED 回复 </p>
<p>事务队列是按照FIFO的⽅式保存⼊队的命令 </p>
<p>3、事务执⾏ </p>
<p>客户端发送 EXEC 命令，服务器执⾏ EXEC 命令逻辑。 </p>
<ul>
<li><p>如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识，或者包含 REDIS_DIRTY_CAS 或者 REDIS_DIRTY_EXEC 标识，那么就直接取消事务的执⾏。 </p>
</li>
<li><p>否则客户端处于事务状态（flags 有 REDIS_MULTI 标识），服务器会遍历客户端的事务队列，然 后执⾏事务队列中的所有命令，最后将返回结果全部返回给客户端；</p>
</li>
</ul>
<p>redis 不⽀持事务回滚机制，但是它会检查每⼀个事务中的命令是否错误。 </p>
<p>Redis 事务不⽀持检查那些程序员⾃⼰逻辑错误。例如对 String 类型的数据库键执⾏对 HashMap 类型 的操作！</p>
<ul>
<li><p>WATCH 命令是⼀个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）⾏为。可以监控⼀ </p>
<p>个或多个键，⼀旦其中有⼀个键被修改（或删除），之后的事务就不会执⾏，监控⼀直持续到 </p>
<p>EXEC命令。 </p>
</li>
<li><p>MULTI命令⽤于开启⼀个事务，它总是返回OK。MULTI执⾏之后，客户端可以继续向服务器发送 </p>
<p>任意多条命令，这些命令不会⽴即被执⾏，⽽是被放到⼀个队列中，当EXEC命令被调⽤时，所有 </p>
<p>队列中的命令才会被执⾏。 </p>
</li>
<li><p>EXEC：执⾏所有事务块内的命令。返回事务块内所有命令的返回值，按命令执⾏的先后顺序排 </p>
<p>列。当操作被打断时，返回空值 nil 。 </p>
</li>
<li><p>通过调⽤DISCARD，客户端可以清空事务队列，并放弃执⾏事务， 并且客户端会从事务状态中退 </p>
<p>出。</p>
</li>
<li><p>UNWATCH命令可以取消watch对所有key的监控。</p>
</li>
</ul>
<h2 id="四、Redis-主从复制的核⼼原理"><a href="#四、Redis-主从复制的核⼼原理" class="headerlink" title="四、Redis 主从复制的核⼼原理"></a>四、Redis 主从复制的核⼼原理</h2><p>通过执⾏slaveof命令或设置slaveof选项，让⼀个服务器去复制另⼀个服务器的数据。主数据库可以进 ⾏读写操作，当写操作导致数据变化时会⾃动将数据同步给从数据库。⽽从数据库⼀般是只读的，并接 受主数据库同步过来的数据。⼀个主数据库可以拥有多个从数据库，⽽⼀个从数据库只能拥有⼀个主数 据库。 </p>
<p>全量复制： </p>
<ol>
<li><p>主节点通过bgsave命令fork⼦进程进⾏RDB持久化，该过程是⾮常消耗CPU、内存(⻚表复制)、硬 盘IO的 </p>
</li>
<li><p>主节点通过⽹络将RDB⽂件发送给从节点，对主从节点的带宽都会带来很⼤的消耗 </p>
</li>
<li><p>从节点清空⽼数据、载⼊新RDB⽂件的过程是阻塞的，⽆法响应客户端的命令；如果从节点执⾏ bgrewriteaof，也会带来额外的消耗</p>
</li>
</ol>
<p>部分复制： </p>
<ol>
<li><p>复制偏移量：执⾏复制的双⽅，主从节点，分别会维护⼀个复制偏移量offset </p>
</li>
<li><p>复制积压缓冲区：主节点内部维护了⼀个固定⻓度的、先进先出(FIFO)队列 作为复制积压缓冲区， 当主从节点offset的差距过⼤超过缓冲区⻓度时，将⽆法执⾏部分复制，只能执⾏全量复制。 </p>
</li>
<li><p>服务器运⾏ID(runid)：每个Redis节点，都有其运⾏ID，运⾏ID由节点在启动时⾃动⽣成，主节点会 将⾃⼰的运⾏ID发送给从节点，从节点会将主节点的运⾏ID存起来。 从节点Redis断开重连的时 候，就是根据运⾏ID来判断同步的进度：</p>
</li>
</ol>
<ul>
<li><p>如果从节点保存的runid与主节点现在的runid相同，说明主从节点之前同步过，主节点会继续 尝试使⽤部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况)； </p>
</li>
<li><p>如果从节点保存的runid与主节点现在的runid不同，说明从节点在断线前同步的Redis节点并不 是当前的主节点，只能进⾏全量复制。</p>
</li>
</ul>
<h2 id="五、Redis有哪些数据结构？分别有哪些典型的应⽤场景？"><a href="#五、Redis有哪些数据结构？分别有哪些典型的应⽤场景？" class="headerlink" title="五、Redis有哪些数据结构？分别有哪些典型的应⽤场景？"></a>五、Redis有哪些数据结构？分别有哪些典型的应⽤场景？</h2><h4 id="Redis的基本数据类型有："><a href="#Redis的基本数据类型有：" class="headerlink" title="Redis的基本数据类型有："></a>Redis的基本数据类型有：</h4><ol>
<li><p>字符串：可以⽤来做最简单的数据，可以缓存某个简单的字符串，也可以缓存某个json格式的字符 串，Redis分布式锁的实现就利⽤了这种数据结构，还包括可以实现计数器、Session共享、分布式 ID </p>
</li>
<li><p>哈希表：可以⽤来存储⼀些key-value对，更适合⽤来存储对象 </p>
</li>
<li><p>列表：Redis的列表通过命令的组合，既可以当做栈，也可以当做队列来使⽤，可以⽤来缓存类似 微信公众号、微博等消息流数据 </p>
</li>
<li><p>集合：和列表类似，也可以存储多个元素，但是不能重复，集合可以进⾏交集、并集、差集操作， 从⽽可以实现类似，我和某⼈共同关注的⼈、朋友圈点赞等功能 </p>
</li>
<li><p>有序集合：集合是⽆序的，有序集合可以设置顺序，可以⽤来实现排⾏榜功能</p>
</li>
</ol>
<h4 id="三种特殊的数据类型："><a href="#三种特殊的数据类型：" class="headerlink" title="三种特殊的数据类型："></a><strong>三种特殊的数据类型</strong>：</h4><p>1、Bitmap：位图，Bitmap想象成一个以位为单位数组，数组中的每个单元只能存0或者1，数组的下标在Bitmap中叫做偏移量。使用Bitmap实现统计功能，更省空间。如果只需要统计数据的二值状态，例如商品有没有、用户在不在等，就可以使用 Bitmap，因为它只用一个 bit 位就能表示 0 或 1。</p>
<p>2、Hyperloglog。HyperLogLog 是一种用于统计基数的数据集合类型，HyperLogLog 的优点是，在输入元素的数量或者体积非常非常大时，计算基数所需的空间总是固定 的、并且是很小的。每个 HyperLogLog 键只需要花费 12 KB 内存，就可以计算接近 2^64 个不同元素的基 数。场景：统计网页的UV（即Unique Visitor，不重复访客，一个人访问某个网站多次，但是还是只计算为一次）。要注意，HyperLogLog 的统计规则是基于概率完成的，所以它给出的统计结果是有一定误差的，标准误算率是 0.81%。</p>
<p>3、Geospatial ：主要用于存储地理位置信息，并对存储的信息进行操作，适用场景如朋友的定位、附近的人、打车距离计算等。</p>
<h2 id="六、Redis分布式锁底层是如何实现的？"><a href="#六、Redis分布式锁底层是如何实现的？" class="headerlink" title="六、Redis分布式锁底层是如何实现的？"></a>六、Redis分布式锁底层是如何实现的？</h2><ol>
<li><p>⾸先利⽤setnx来保证：如果key不存在才能获取到锁，如果key存在，则获取不到锁 </p>
</li>
<li><p>然后还要利⽤lua脚本来保证多个redis操作的原⼦性 </p>
</li>
<li><p>同时还要考虑到锁过期，所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约 </p>
</li>
<li><p>同时还要考虑到redis节点挂掉后的情况，所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁， 都申请到了才证明获取锁成功，这样就算其中某个redis节点挂掉了，锁也不能被其他客户端获取到</p>
</li>
</ol>
<h2 id="七、Redis主从复制"><a href="#七、Redis主从复制" class="headerlink" title="七、Redis主从复制"></a>七、Redis主从复制</h2><h3 id="1、核⼼原理"><a href="#1、核⼼原理" class="headerlink" title="1、核⼼原理"></a>1、核⼼原理</h3><p>Redis的主从复制是提⾼Redis的可靠性的有效措施，主从复制的流程如下： </p>
<ol>
<li><p>集群启动时，主从库间会先建⽴连接，为全量复制做准备 </p>
</li>
<li><p>主库将所有数据同步给从库。从库收到数据后，在本地完成数据加载，这个过程依赖于内存快照 RDB </p>
</li>
<li><p>在主库将数据同步给从库的过程中，主库不会阻塞，仍然可以正常接收请求。否则，redis的服务就被中断了。但是，这些请求中的写操作并没有记录到刚刚⽣成的RDB⽂件中。为了保证主从库的数据⼀致性，主库会在内存中⽤专⻔的replication buffer，记录RDB⽂件⽣成收到的所有写操作。 </p>
</li>
<li><p>最后，也就是第三个阶段，主库会把第⼆阶段执⾏过程中新收到的写命令，再发送给从库。具体的操作 是，当主库完成RDB⽂件发送后，就会把此时replocation buffer中修改操作发送给从库，从库再执⾏这些操作。这样⼀来，主从库就实现同步了 </p>
</li>
<li><p>后续主库和从库都可以处理客户端读操作，写操作只能交给主库处理，主库接收到写操作后，还会将写操作发送给从库，实现增量同步</p>
</li>
</ol>
<p><strong>复制原理：</strong></p>
<ul>
<li>l Slave启动成功连接到master后会发送一个sync命令</li>
<li>l Master接到命令启动后台的存盘进程，同时收集所有接收到的用于修改数据集命令， 在后台进程执行完毕之后，master将传送整个数据文件到slave,以完成一次完全同步</li>
<li>l 全量复制：而slave服务在接收到数据库文件数据后，将其存盘并加载到内存中。</li>
<li>l 增量复制：Master继续将新的所有收集到的修改命令依次传给slave,完成同步</li>
<li>l 但是只要是重新连接master,一次完全同步（全量复制)将被自动执行</li>
</ul>
<h3 id="2、优点"><a href="#2、优点" class="headerlink" title="2、优点"></a>2、优点</h3><ul>
<li><p>读写分离，性能扩展</p>
</li>
<li><p>容灾快速恢复</p>
</li>
</ul>
<h3 id="3、主从架构数据丢失问题"><a href="#3、主从架构数据丢失问题" class="headerlink" title="3、主从架构数据丢失问题"></a>3、主从架构数据丢失问题</h3><p>数据丢失的问题是不可避免的，但是我们可以尽量减少。</p>
<p>在Redis的配置文件里设置参数</p>
<pre><code>min-slaves-to-write 1
min-slaves-max-lag 10
</code></pre>
<p><code>min-slaves-to-write</code>默认情况下是0，<code>min-slaves-max-lag</code>默认情况下是10。</p>
<p>上面的配置的意思是要求至少有1个slave，数据复制和同步的延迟不能超过10秒。如果说一旦所有的slave，数据复制和同步的延迟都超过了10秒钟，那么这个时候，master就不会再接收任何请求了。</p>
<p>减小<code>min-slaves-max-lag</code>参数的值，这样就可以避免在发生故障时大量的数据丢失，一旦发现延迟超过了该值就不会往master中写入数据。</p>
<p>那么对于client，我们可以采取降级措施，将数据暂时写入本地缓存和磁盘中，在一段时间后重新写入master来保证数据不丢失；也可以将数据写入kafka消息队列，隔一段时间去消费kafka中的数据。</p>
<h2 id="八、Redis集群策略"><a href="#八、Redis集群策略" class="headerlink" title="八、Redis集群策略"></a>八、Redis集群策略</h2><p>Redis提供了三种集群策略： </p>
<ol>
<li><p>主从模式：这种模式⽐较简单，主库可以读写，并且会和从库进⾏数据同步，这种模式下，客户端 直接连主库或某个从库，但是但主库或从库宕机后，客户端需要⼿动修改IP，另外，这种模式也⽐ 较难进⾏扩容，整个集群所能存储的数据受到某台机器的内存容量，所以不可能⽀持特⼤数据量 </p>
</li>
<li><p>哨兵模式：这种模式在主从的基础上新增了哨兵节点，但主库节点宕机后，哨兵会发现主库节点宕 机，然后在从库中选择⼀个库作为进的主库，另外哨兵也可以做集群，从⽽可以保证但某⼀个哨兵 节点宕机后，还有其他哨兵节点可以继续⼯作，这种模式可以⽐较好的保证Redis集群的⾼可⽤， 但是仍然不能很好的解决Redis的容量上限问题。 </p>
</li>
<li><p>Cluster模式：Cluster模式是⽤得⽐较多的模式，它⽀持多主多从，这种模式会按照key进⾏槽位的 分配，可以使得不同的key分散到不同的主节点上，利⽤这种模式可以使得整个集群⽀持更⼤的数 据容量，同时每个主节点可以拥有⾃⼰的多个从节点，如果该主节点宕机，会从它的从节点中选举 ⼀个新的主节点。</p>
</li>
</ol>
<p>对于这三种模式，如果Redis要存的数据量不⼤，可以选择哨兵模式，如果Redis要存的数据量⼤，并且 需要持续的扩容，那么选择Cluster模式。</p>
<h2 id="九、缓存穿透、缓存击穿、缓存雪崩分别是什么"><a href="#九、缓存穿透、缓存击穿、缓存雪崩分别是什么" class="headerlink" title="九、缓存穿透、缓存击穿、缓存雪崩分别是什么"></a>九、缓存穿透、缓存击穿、缓存雪崩分别是什么</h2><p>缓存中存放的⼤多都是热点数据，⽬的就是防⽌请求可以直接从缓存中获取到数据，⽽不⽤访问 Mysql。</p>
<ol>
<li><p>缓存雪崩：如果缓存中某⼀时刻⼤批热点数据同时过期，那么就可能导致⼤量请求直接访问Mysql 了，解决办法就是在过期时间上增加⼀点随机值，另外如果搭建⼀个⾼可⽤的Redis集群也是防⽌ 缓存雪崩的有效⼿段 </p>
</li>
<li><p>缓存击穿：和缓存雪崩类似，缓存雪崩是⼤批热点数据失效，⽽缓存击穿是指某⼀个热点key突然 失效，也导致了⼤量请求直接访问Mysql数据库，这就是缓存击穿，解决⽅案就是考虑这个热点key 不设过期时间 </p>
</li>
<li><p>缓存穿透：假如某⼀时刻访问redis的⼤量key都在redis中不存在（⽐如⿊客故意伪造⼀些乱七⼋糟 的key），那么也会给数据造成压⼒，这就是缓存穿透，解决⽅案是使⽤布隆过滤器，它的作⽤就 是如果它认为⼀个key不存在，那么这个key就肯定不存在，所以可以在缓存之前加⼀层布隆过滤器 来拦截不存在的key</p>
</li>
</ol>
<h2 id="十、Redis和Mysql如何保证数据⼀致"><a href="#十、Redis和Mysql如何保证数据⼀致" class="headerlink" title="十、Redis和Mysql如何保证数据⼀致"></a>十、Redis和Mysql如何保证数据⼀致</h2><ol>
<li><p>先更新Mysql，再更新Redis，如果更新Redis失败，可能仍然不⼀致 </p>
</li>
<li><p>先删除Redis缓存数据，再更新Mysql，再次查询的时候在将数据添加到缓存中，这种⽅案能解决1 ⽅案的问题，但是在⾼并发下性能较低，⽽且仍然会出现数据不⼀致的问题，⽐如线程1删除了 Redis缓存数据，正在更新Mysql，此时另外⼀个查询再查询，那么就会把Mysql中⽼数据⼜查到 Redis中 </p>
</li>
<li><p>延时双删，步骤是：先删除Redis缓存数据，再更新Mysql，延迟⼏百毫秒再删除Redis缓存数据， 这样就算在更新Mysql时，有其他线程读了Mysql，把⽼数据读到了Redis中，那么也会被删除掉， 从⽽把数据保持⼀致</p>
</li>
</ol>
<h2 id="十一、Redis的持久化机制"><a href="#十一、Redis的持久化机制" class="headerlink" title="十一、Redis的持久化机制"></a>十一、Redis的持久化机制</h2><p>RDB：Redis DataBase 将某⼀个时刻的内存快照（Snapshot），以⼆进制的⽅式写⼊磁盘。 </p>
<p>⼿动触发： </p>
<ul>
<li><p>save命令，使 Redis 处于阻塞状态，直到 RDB 持久化完成，才会响应其他客户端发来的命令，所 以在⽣产环境⼀定要慎⽤ </p>
</li>
<li><p>bgsave命令，fork出⼀个⼦进程执⾏持久化，主进程只在fork过程中有短暂的阻塞，⼦进程创建 之后，主进程就可以响应客户端请求了 </p>
</li>
<li><p>⾃动触发： </p>
</li>
<li><p>save m n ：在 m 秒内，如果有 n 个键发⽣改变，则⾃动触发持久化，通过bgsave执⾏，如果设 置多个、只要满⾜其⼀就会触发，配置⽂件有默认配置(可以注释掉) </p>
</li>
<li><p>flushall：⽤于清空redis所有的数据库，flushdb清空当前redis所在库数据(默认是0号数据库)，会 清空RDB⽂件，同时也会⽣成dump.rdb、内容为空 </p>
</li>
<li><p>主从同步：全量同步时会⾃动触发bgsave命令，⽣成rdb发送给从节点</p>
</li>
</ul>
<p>优点： </p>
<ol>
<li><p>整个Redis数据库将只包含⼀个⽂件 dump.rdb，⽅便持久化。 </p>
</li>
<li><p>容灾性好，⽅便备份。 </p>
</li>
<li><p>性能最⼤化，fork ⼦进程来完成写操作，让主进程继续处理命令，所以是 IO 最⼤化。使⽤单独⼦ 进程来进⾏持久化，主进程不会进⾏任何 IO 操作，保证了 redis 的⾼性能 </p>
</li>
<li><p>相对于数据集⼤时，⽐ AOF的启动效率更⾼。</p>
</li>
</ol>
<p>缺点： </p>
<ol>
<li><p>数据安全性低。RDB 是间隔⼀段时间进⾏持久化，如果持久化之间 redis 发⽣故障，会发⽣数据丢 失。所以这种⽅式更适合数据要求不严谨的时候) </p>
</li>
<li><p>由于RDB是通过fork⼦进程来协助完成数据持久化⼯作的，因此，如果当数据集较⼤时，可能会导 致整个服务器停⽌服务⼏百毫秒，甚⾄是1秒钟。会占⽤cpu</p>
</li>
</ol>
<p>AOF：Append Only File 以⽇志的形式记录服务器所处理的每⼀个写、删除操作，查询操作不会记录， 以⽂本的⽅式记录，可以打开⽂件看到详细的操作记录，调操作系统命令进程刷盘 </p>
<ol>
<li><p>所有的写命令会追加到 AOF 缓冲中。 </p>
</li>
<li><p>AOF 缓冲区根据对应的策略向硬盘进⾏同步操作。 </p>
</li>
<li><p>随着 AOF ⽂件越来越⼤，需要定期对 AOF ⽂件进⾏重写，达到压缩的⽬的。 </p>
</li>
<li><p>当 Redis 重启时，可以加载 AOF ⽂件进⾏数据恢复。同步策略：</p>
</li>
</ol>
<p>每秒同步：异步完成，效率⾮常⾼，⼀旦系统出现宕机现象，那么这⼀秒钟之内修改的数据将会丢 失 </p>
<p>每修改同步：同步持久化，每次发⽣的数据变化都会被⽴即记录到磁盘中，最多丢⼀条 不同步：由操作 系统控制，可能丢失较多数据 </p>
<p>优点： </p>
<ol>
<li><p>数据安全 </p>
</li>
<li><p>通过 append 模式写⽂件，即使中途服务器宕机也不会破坏已经存在的内容，可以通过 redis- check-aof ⼯具解决数据⼀致性问题。 </p>
</li>
<li><p>AOF 机制的 rewrite 模式。定期对AOF⽂件进⾏重写，以达到压缩的⽬的</p>
</li>
</ol>
<p>缺点： </p>
<ol>
<li><p>AOF ⽂件⽐ RDB ⽂件⼤，且恢复速度慢。 </p>
</li>
<li><p>数据集⼤的时候，⽐ rdb 启动效率低。 </p>
</li>
<li><p>运⾏效率没有RDB⾼</p>
</li>
</ol>
<p>对⽐： </p>
<ul>
<li><p>AOF⽂件⽐RDB更新频率⾼，优先使⽤AOF还原数据。AOF⽐RDB更安全也更⼤ </p>
</li>
<li><p>RDB性能⽐AOF好</p>
</li>
<li><p>如果两个都配了优先加载AOF</p>
</li>
</ul>
<h2 id="十二、Redis单线程为什么这么快"><a href="#十二、Redis单线程为什么这么快" class="headerlink" title="十二、Redis单线程为什么这么快"></a>十二、Redis单线程为什么这么快</h2><p>Redis基于Reactor模式开发了⽹络事件处理器、⽂件事件处理器 fileeventhandler。它是单线程的， 所 以 Redis才叫做单线程的模型，它采⽤IO多路复⽤机制来同时监听多个Socket，根据Socket上的事件类 型来选择对应的事件处理器来处理这个事件。可以实现⾼性能的⽹络通信模型，⼜可以跟内部其他单 线程的模块进⾏对接，保证了 Redis内部的线程模型的简单性。⽂件事件处理器的结构包含4个部分：多个Socket、IO多路复⽤程序、⽂件事件分派器以及事件处理器（命令请求处理器、命令回复处理器、连接应答处理器等）。多个 Socket 可能并发的产⽣不同的事件，IO多路复⽤程序会监听多个 Socket，会将 Socket 放⼊⼀个队列中排队，每次从队列中有序、同步取出⼀个 Socket 给事件分派器，事件分派器把 Socket 给对应的事件处理器。 </p>
<p>然后⼀个 Socket 的事件处理完之后，IO多路复⽤程序才会将队列中的下⼀个 Socket 给事件分派器。 ⽂件事件分派器会根据每个 Socket 当前产⽣的事件，来选择对应的事件处理器来处理。 </p>
<ol>
<li><p>Redis启动初始化时，将连接应答处理器跟AE_READABLE事件关联。 </p>
</li>
<li><p>若⼀个客户端发起连接，会产⽣⼀个AE_READABLE事件，然后由连接应答处理器负责和客户端建 ⽴ 连接，创建客户端对应的socket，同时将这个socket的AE_READABLE事件和命令请求处理 器关联，使 得客户端可以向主服务器发送命令请求。 </p>
</li>
<li><p>当客户端向Redis发请求时（不管读还是写请求），客户端socket都会产⽣⼀个AE_READABLE事 件，触发命令请求处理器。处理器读取客户端的命令内容， 然后传给相关程序执⾏。 </p>
</li>
<li><p>当Redis服务器准备好给客户端的响应数据后，会将socket的AE_WRITABLE事件和命令回复处理 器关联，当客户端准备好读取响应数据时，会在socket产⽣⼀个AE_WRITABLE事件，由对应命令 回复处 理器处理，即将准备好的响应数据写⼊socket，供客户端读取。 </p>
</li>
<li><p>命令回复处理器全部写完到 socket 后，就会删除该socket的AE_WRITABLE事件和命令回复处理 器的映射。</p>
</li>
</ol>
<p>单线程快的原因： </p>
<ol>
<li><p>纯内存操作 </p>
</li>
<li><p>核⼼是基于⾮阻塞的IO多路复⽤机制 </p>
</li>
<li><p>单线程反⽽避免了多线程的频繁上下⽂切换带来的性能问题</p>
</li>
</ol>
<h2 id="十三、简述Redis事务实现"><a href="#十三、简述Redis事务实现" class="headerlink" title="十三、简述Redis事务实现"></a>十三、简述Redis事务实现</h2><p>事务开始：MULTI命令的执⾏，标识着⼀个事务的开始。MULTI命令会将客户端状态的 flags属性 中打开REDIS_MULTI标识来完成的。 </p>
<p>命令⼊队：当⼀个客户端切换到事务状态之后，服务器会根据这个客户端发送来的命令来执⾏不同 的操作。如果客 户端发送的命令为MULTI、EXEC、WATCH、DISCARD中的⼀个，⽴即执⾏这个 命令，否则将命令放⼊⼀ 个事务队列⾥⾯，然后向客户端返回QUEUED回复，如果客户端发送的 命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中⼀个，那么服务器⽴即执⾏这个命令。如果客户端发送的是四个命令以外的其他命令，那么服务器并不⽴即执⾏这个命令。⾸先检查此命令的格式是否正确，如果不正确，服务器会在客户端状态（redisClient）的 flags 属性关闭 REDIS_MULTI 标识，并且返回错误信息给客户端。如果正确，将这个命令放⼊⼀个事务队列⾥⾯，然后向客户端返回 QUEUED 回复事务队列是按照FIFO的⽅式保存⼊队的命令 </p>
<p>事务执⾏：客户端发送 EXEC 命令，服务器执⾏ EXEC 命令逻辑。如果客户端状态的 flags 属性 不包含 REDIS_MULTI 标识，或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识，那么 就直接取消事务的执⾏。 否则客户端处于事务状态（flags有 REDIS_MULTI 标识），服务器会遍 历客户端的事务队列，然后执⾏事务队列中的所有命令，最后将返回结果全部返回给客户端； Redis不⽀持事务回滚机制，但是它会检查每⼀个事务中的命令是否错误。Redis事务不⽀持检查那些程序员⾃⼰逻辑错误。例如对 String 类型的数据库键执⾏对 HashMap 类型的操作！ </p>
<h2 id="十四、Redis的常用场景有哪些"><a href="#十四、Redis的常用场景有哪些" class="headerlink" title="十四、Redis的常用场景有哪些?"></a>十四、Redis的常用场景有哪些?</h2><p><strong>1、缓存</strong></p>
<p>缓存现在几乎是所有中大型网站都在用的必杀技，合理的利用缓存不仅能够提升网站访问速度，还能大大降低数据库的压力。Redis提供了键过期功能，也提供了灵活的键淘汰策略，所以，现在Redis用在缓存的场合非常多。</p>
<p><strong>2、排行榜</strong></p>
<p>很多网站都有排行榜应用的，如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。</p>
<p><strong>3、计数器</strong></p>
<p>什么是计数器，如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效，每次浏览都得给+1，并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能，内存操作，性能非常好，非常适用于这些计数场景。</p>
<p><strong>4、分布式会话</strong></p>
<p>集群模式下，在应用不多的情况下一般使用容器自带的session复制功能就能满足，当应用增多相对复杂的系统中，一般都会搭建以Redis等内存数据库为中心的session服务，session不再由容器管理，而是由session服务及内存数据库管理。</p>
<p><strong>5、分布式锁</strong></p>
<p>在很多互联网公司中都使用了分布式技术，分布式技术带来的技术挑战是对同一个资源的并发访问，如全局ID、减库存、秒杀等场景，并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现，但在并发量高的场合中，利用数据库锁来控制资源的并发访问是不太理想的，大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁，如果设置返回1说明获取锁成功，否则获取锁失败，实际应用中要考虑的细节要更多。</p>
<p><strong>6、 社交网络</strong></p>
<p>点赞、踩、关注/被关注、共同好友等是社交网站的基本功能，社交网站的访问量通常来说比较大，而且传统的关系数据库类型不适合存储这种类型的数据，Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。如在微博中的共同好友，通过Redis的set能够很方便得出。</p>
<p><strong>7、最新列表</strong></p>
<p>Redis列表结构，LPUSH可以在列表头部插入一个内容ID作为关键字，LTRIM可用来限制列表的数量，这样列表永远为N个ID，无需查询最新的列表，直接根据ID去到对应的内容页即可。</p>
<p><strong>8、消息系统</strong></p>
<p>消息队列是大型网站必用中间件，如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件，主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能，能实现一个简单的消息队列系统。另外，这个不能和专业的消息中间件相比。</p>
<h2 id="十五、哨兵模式"><a href="#十五、哨兵模式" class="headerlink" title="十五、哨兵模式"></a>十五、哨兵模式</h2><p>主从模式下，当主服务器宕机后，需要手动把一台从服务器切换为主服务器，这就需要人工干预，费事费力，还会造成一段时间内服务不可用。这种方式并不推荐，实际生产中，我们优先考虑哨兵模式。这种模式下，master 宕机，哨兵会自动选举 master 并将其他的 slave 指向新的 master。</p>
<p>Redis Sentinel是社区版本推出的原生高可用解决方案，其部署架构主要包括两部分：Redis Sentinel集群和Redis数据集群。</p>
<p>其中Redis Sentinel集群是由若干Sentinel节点组成的分布式集群，可以实现故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel的节点数量要满足2n+1（n&gt;=1）的奇数个。</p>
<p><img src="https://notes-hub.oss-cn-beijing.aliyuncs.com/typora_images/image-20210829103343110.png" alt="image-20210829103343110"></p>
<p><strong>优点：</strong></p>
<ul>
<li>Redis Sentinel集群部署简单；</li>
<li>能够解决Redis主从模式下的高可用切换问题；</li>
<li>很方便实现Redis数据节点的线形扩展，轻松突破Redis自身单线程瓶颈，可极大满足Redis大容量或高性能的业务需求；</li>
<li>可以实现一套Sentinel监控一组Redis数据节点或多组数据节点。</li>
</ul>
<p><strong>缺点：</strong></p>
<ul>
<li>部署相对Redis主从模式要复杂一些，原理理解更繁琐；</li>
<li>资源浪费，Redis数据节点中slave节点作为备份节点不提供服务；</li>
<li>Redis Sentinel主要是针对Redis数据节点中的主节点的高可用切换，对Redis的数据节点做失败判定分为主观下线和客观下线两种，对于Redis的从节点有对节点做主观下线操作，并不执行故障转移。</li>
<li>不能解决读写分离问题，实现起来相对复杂。</li>
</ul>
<h3 id="1、哨兵是如何工作的"><a href="#1、哨兵是如何工作的" class="headerlink" title="1、哨兵是如何工作的"></a>1、哨兵是如何工作的</h3><ol>
<li>每个Sentinel以每秒钟一次的频率向它所知的Master，Slave以及其他 Sentinel 实例发送一个 PING 命令。</li>
<li>如果一个实例（instance）距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值， 则这个实例会被当前 Sentinel 标记为主观下线。</li>
<li>如果一个Master被标记为主观下线，则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。</li>
<li>当有足够数量的 Sentinel（大于等于配置文件指定的值）在指定的时间范围内确认Master的确进入了主观下线状态， 则Master会被标记为客观下线 。</li>
<li>当Master被 Sentinel 标记为客观下线时，Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 （在一般情况下， 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master，Slave发送 INFO 命令 ）。</li>
<li>若没有足够数量的 Sentinel 同意 Master 已经下线， Master 的客观下线状态就会变成主观下线。若 Master 重新向 Sentinel 的 PING 命令返回有效回复， Master 的主观下线状态就会被移除。</li>
<li>sentinel节点会与其他sentinel节点进行“沟通”，投票选举一个sentinel节点进行故障处理，在从节点中选取一个主节点，其他从节点挂载到新的主节点上自动复制新主节点的数据。</li>
</ol>
<h3 id="2、故障转移时重新选举master的标准是什么"><a href="#2、故障转移时重新选举master的标准是什么" class="headerlink" title="2、故障转移时重新选举master的标准是什么"></a>2、故障转移时重新选举master的标准是什么</h3><p>如果一个master被认为odown了，而且majority哨兵都允许了主备切换，那么某个哨兵就会执行主备切换操作，此时首先要选举一个slave来，会考虑slave的一些信息。</p>
<ul>
<li>跟master断开连接的时长。 如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍，外加master宕机的时长，那么slave就被认为不适合选举为master.</li>
</ul>
<pre><code>( down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
</code></pre>
<ul>
<li>slave优先级。 按照slave优先级进行排序，slave priority越低，优先级就越高</li>
<li>复制offset。 如果slave priority相同，那么看replica offset，哪个slave复制了越多的数据，offset越靠后，优先级就越高</li>
<li>run id 如果上面两个条件都相同，那么选择一个run id比较小的那个slave。</li>
</ul>
<h3 id="3、同步配置的时候其他哨兵根据什么更新自己的配置？"><a href="#3、同步配置的时候其他哨兵根据什么更新自己的配置？" class="headerlink" title="3、同步配置的时候其他哨兵根据什么更新自己的配置？"></a>3、同步配置的时候其他哨兵根据什么更新自己的配置？</h3><p>执行切换的那个哨兵，会从要切换到的新master（salve-&gt;master）那里得到一个configuration epoch，这就是一个version号，每次切换的version号都必须是唯一的。</p>
<p>如果第一个选举出的哨兵切换失败了，那么其他哨兵，会等待failover-timeout时间，然后接替继续执行切换，此时会重新获取一个新的configuration epoch 作为新的version号。</p>
<p>这个version号就很重要了，因为各种消息都是通过一个channel去发布和监听的，所以一个哨兵完成一次新的切换之后，新的master配置是跟着新的version号的，其他的哨兵都是根据版本号的大小来更新自己的master配置的。</p>
<h2 id="十六、分布式问题"><a href="#十六、分布式问题" class="headerlink" title="十六、分布式问题"></a>十六、分布式问题</h2><h3 id="1、什么是分布式锁？为什么要用分布式锁？"><a href="#1、什么是分布式锁？为什么要用分布式锁？" class="headerlink" title="1、什么是分布式锁？为什么要用分布式锁？"></a>1、什么是分布式锁？为什么要用分布式锁？</h3><p>锁在程序中的作用就是同步工具，保证共享资源在同一时刻只能被一个线程访问，Java中的锁我们都很熟悉了，像synchronized 、Lock都是我们经常使用的，但是Java的锁只能保证单机的时候有效，分布式集群环境就无能为力了，这个时候我们就需要用到分布式锁。</p>
<p>分布式锁，顾名思义，就是分布式项目开发中用到的锁，可以用来控制分布式系统之间同步访问共享资源。</p>
<p>思路是：在整个系统提供一个<strong>全局、唯一</strong>的获取锁的“东西”，然后每个系统在需要加锁时，都去问这个“东西”拿到一把锁，这样不同的系统拿到的就可以认为是同一把锁。至于这个“东西”，可以是Redis、Zookeeper，也可以是数据库。</p>
<p>一般来说，分布式锁需要满足的特性有这么几点：</p>
<p>1、互斥性：在任何时刻，对于同一条数据，只有一台应用可以获取到分布式锁；</p>
<p>2、高可用性：在分布式场景下，一小部分服务器宕机不影响正常使用，这种情况就需要将提供分布式锁的服务以集群的方式部署；</p>
<p>3、防止锁超时：如果客户端没有主动释放锁，服务器会在一段时间之后自动释放锁，防止客户端宕机或者网络不可达时产生死锁；</p>
<p>4、独占性：加锁解锁必须由同一台服务器进行，也就是锁的持有者才可以释放锁，不能出现你加的锁，别人给你解锁了。</p>
<h3 id="2、Redis分布式锁"><a href="#2、Redis分布式锁" class="headerlink" title="2、Redis分布式锁"></a>2、Redis分布式锁</h3><p>1、加锁</p>
<p>使用setnx来加锁。key是锁的唯一标识，按业务来决定命名，value这里设置为test。</p>
<pre><code>setx key test
</code></pre>
<p>当一个线程执行setnx返回1，说明key原本不存在，该线程成功得到了锁；当一个线程执行setnx返回0，说明key已经存在，该线程抢锁失败；</p>
<p>2、解锁</p>
<p>有加锁就得有解锁。当得到的锁的线程执行完任务，需要释放锁，以便其他线程可以进入。释放锁的最简单方式就是执行del指令。</p>
<pre><code>del key
</code></pre>
<p>释放锁之后，其他线程就可以继续执行setnx命令来获得锁。</p>
<p>3、锁超时</p>
<p>锁超时知道的是：如果一个得到锁的线程在执行任务的过程中挂掉，来不及显式地释放锁，这块资源将会永远被锁住，别的线程北向进来。</p>
<p>所以，setnx的key必须设置一个超时时间，以保证即使没有被显式释放，这把锁也要在一段时间后自动释放。setnx不支持超时参数，所以需要额外指令，</p>
<pre><code>expire key 30
</code></pre>
<p>上述分布式锁存在的问题：</p>
<p><strong>通过上述<code>setnx</code> 、<code>del</code>和<code>expire</code>实现的分布式锁还是存在着一些问题。</strong></p>
<p>1、SETNX 和 EXPIRE 非原子性</p>
<p>假设一个场景中，某一个线程刚执行setnx，成功得到了锁。此时setnx刚执行成功，还未来得及执行expire命令，节点就挂掉了。此时这把锁就没有设置过期时间，别的线程就再也无法获得该锁。</p>
<p><strong>解决措施:</strong></p>
<p>由于<code>setnx</code>指令本身是不支持传入超时时间的，而在Redis2.6.12版本上为<code>set</code>指令增加了可选参数, 用法如下：</p>
<pre><code>SET key value [EX seconds][PX milliseconds] [NX|XX]
</code></pre>
<ul>
<li>EX second: 设置键的过期时间为second秒；</li>
<li>PX millisecond：设置键的过期时间为millisecond毫秒；</li>
<li>NX：只在键不存在时，才对键进行设置操作；</li>
<li>XX：只在键已经存在时，才对键进行设置操作；</li>
<li>SET操作完成时，返回OK，否则返回nil。</li>
</ul>
<p>2、锁误解除</p>
<p>如果线程 A 成功获取到了锁，并且设置了过期时间 30 秒，但线程 A 执行时间超过了 30 秒，锁过期自动释放，此时线程 B 获取到了锁；随后 A 执行完成，线程 A 使用 DEL 命令来释放锁，但此时线程 B 加的锁还没有执行完成，线程 A 实际释放的线程 B 加的锁。</p>
<p><strong>解决办法：</strong></p>
<p>在del释放锁之前加一个判断，验证当前的锁是不是自己加的锁。</p>
<p>具体在加锁的时候把当前线程的id当做value，可生成一个 UUID 标识当前线程，在删除之前验证key对应的value是不是自己线程的id。</p>
<p>还可以使用 lua 脚本做验证标识和解锁操作。</p>
<p>3、超时解锁导致并发</p>
<p>如果线程 A 成功获取锁并设置过期时间 30 秒，但线程 A 执行时间超过了 30 秒，锁过期自动释放，此时线程 B 获取到了锁，线程 A 和线程 B 并发执行。</p>
<p>A、B 两个线程发生并发显然是不被允许的，一般有两种方式解决该问题：</p>
<ul>
<li>将过期时间设置足够长，确保代码逻辑在锁释放之前能够执行完成。</li>
<li>为获取锁的线程增加守护线程，为将要过期但未释放的锁增加有效时间。</li>
</ul>
<p>4、不可重入</p>
<p>当线程在持有锁的情况下再次请求加锁，如果一个锁支持一个线程多次加锁，那么这个锁就是可重入的。如果一个不可重入锁被再次加锁，由于该锁已经被持有，再次加锁会失败。Redis 可通过对锁进行重入计数，加锁时加 1，解锁时减 1，当计数归 0 时释放锁。</p>
<p>5、无法等待锁释放</p>
<p>上述命令执行都是立即返回的，如果客户端可以等待锁释放就无法使用。</p>
<ul>
<li>可以通过客户端轮询的方式解决该问题，当未获取到锁时，等待一段时间重新获取锁，直到成功获取锁或等待超时。这种方式比较消耗服务器资源，当并发量比较大时，会影响服务器的效率。</li>
<li>另一种方式是使用 Redis 的发布订阅功能，当获取锁失败时，订阅锁释放消息，获取锁成功后释放时，发送锁释放消息。</li>
</ul>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">YangChao</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://monkey-chao.gitee.io/2022/08/10/java-mian-shi-ti-redis-pian/">https://monkey-chao.gitee.io/2022/08/10/java-mian-shi-ti-redis-pian/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">YangChao</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/tags/Redis/">
                                    <span class="chip bg-color">Redis</span>
                                </a>
                            
                                <a href="/tags/Java/">
                                    <span class="chip bg-color">Java</span>
                                </a>
                            
                                <a href="/tags/%E9%9D%A2%E8%AF%95/">
                                    <span class="chip bg-color">面试</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    
    <div class="livere-card card" data-aos="fade-up">
    <!-- 来必力City版安装代码 -->
    <div id="lv-container" class="card-content" data-id="city" data-uid="">
        <script type="text/javascript">
            (function (d, s) {
                let j, e = d.getElementsByTagName(s)[0];
                if (typeof LivereTower === 'function') {
                    return;
                }

                j = d.createElement(s);
                j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
                j.async = true;

                e.parentNode.insertBefore(j, e);
            })(document, 'script');
        </script>
        <noscript>为正常使用来必力评论功能请激活JavaScript。</noscript>
    </div>
    <!-- City版安装代码已完成 -->
</div>
    

    
        <style>
    .valine-card {
        margin: 1.5rem auto;
    }

    .valine-card .card-content {
        padding: 20px 20px 5px 20px;
    }

    #vcomments textarea {
        box-sizing: border-box;
        background: url("/medias/comment_bg.png") 100% 100% no-repeat;
    }

    #vcomments p {
        margin: 2px 2px 10px;
        font-size: 1.05rem;
        line-height: 1.78rem;
    }

    #vcomments blockquote p {
        text-indent: 0.2rem;
    }

    #vcomments a {
        padding: 0 2px;
        color: #4cbf30;
        font-weight: 500;
        text-decoration: none;
    }

    #vcomments img {
        max-width: 100%;
        height: auto;
        cursor: pointer;
    }

    #vcomments ol li {
        list-style-type: decimal;
    }

    #vcomments ol,
    ul {
        display: block;
        padding-left: 2em;
        word-spacing: 0.05rem;
    }

    #vcomments ul li,
    ol li {
        display: list-item;
        line-height: 1.8rem;
        font-size: 1rem;
    }

    #vcomments ul li {
        list-style-type: disc;
    }

    #vcomments ul ul li {
        list-style-type: circle;
    }

    #vcomments table, th, td {
        padding: 12px 13px;
        border: 1px solid #dfe2e5;
    }

    #vcomments table, th, td {
        border: 0;
    }

    table tr:nth-child(2n), thead {
        background-color: #fafafa;
    }

    #vcomments table th {
        background-color: #f2f2f2;
        min-width: 80px;
    }

    #vcomments table td {
        min-width: 80px;
    }

    #vcomments h1 {
        font-size: 1.85rem;
        font-weight: bold;
        line-height: 2.2rem;
    }

    #vcomments h2 {
        font-size: 1.65rem;
        font-weight: bold;
        line-height: 1.9rem;
    }

    #vcomments h3 {
        font-size: 1.45rem;
        font-weight: bold;
        line-height: 1.7rem;
    }

    #vcomments h4 {
        font-size: 1.25rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    #vcomments h5 {
        font-size: 1.1rem;
        font-weight: bold;
        line-height: 1.4rem;
    }

    #vcomments h6 {
        font-size: 1rem;
        line-height: 1.3rem;
    }

    #vcomments p {
        font-size: 1rem;
        line-height: 1.5rem;
    }

    #vcomments hr {
        margin: 12px 0;
        border: 0;
        border-top: 1px solid #ccc;
    }

    #vcomments blockquote {
        margin: 15px 0;
        border-left: 5px solid #42b983;
        padding: 1rem 0.8rem 0.3rem 0.8rem;
        color: #666;
        background-color: rgba(66, 185, 131, .1);
    }

    #vcomments pre {
        font-family: monospace, monospace;
        padding: 1.2em;
        margin: .5em 0;
        background: #272822;
        overflow: auto;
        border-radius: 0.3em;
        tab-size: 4;
    }

    #vcomments code {
        font-family: monospace, monospace;
        padding: 1px 3px;
        font-size: 0.92rem;
        color: #e96900;
        background-color: #f8f8f8;
        border-radius: 2px;
    }

    #vcomments pre code {
        font-family: monospace, monospace;
        padding: 0;
        color: #e8eaf6;
        background-color: #272822;
    }

    #vcomments pre[class*="language-"] {
        padding: 1.2em;
        margin: .5em 0;
    }

    #vcomments code[class*="language-"],
    pre[class*="language-"] {
        color: #e8eaf6;
    }

    #vcomments [type="checkbox"]:not(:checked), [type="checkbox"]:checked {
        position: inherit;
        margin-left: -1.3rem;
        margin-right: 0.4rem;
        margin-top: -1px;
        vertical-align: middle;
        left: unset;
        visibility: visible;
    }

    #vcomments b,
    strong {
        font-weight: bold;
    }

    #vcomments dfn {
        font-style: italic;
    }

    #vcomments small {
        font-size: 85%;
    }

    #vcomments cite {
        font-style: normal;
    }

    #vcomments mark {
        background-color: #fcf8e3;
        padding: .2em;
    }

    #vcomments table, th, td {
        padding: 12px 13px;
        border: 1px solid #dfe2e5;
    }

    table tr:nth-child(2n), thead {
        background-color: #fafafa;
    }

    #vcomments table th {
        background-color: #f2f2f2;
        min-width: 80px;
    }

    #vcomments table td {
        min-width: 80px;
    }

    #vcomments [type="checkbox"]:not(:checked), [type="checkbox"]:checked {
        position: inherit;
        margin-left: -1.3rem;
        margin-right: 0.4rem;
        margin-top: -1px;
        vertical-align: middle;
        left: unset;
        visibility: visible;
    }
</style>

<div class="card valine-card" data-aos="fade-up">
    <div class="comment_headling" style="font-size: 20px; font-weight: 700; position: relative; padding-left: 20px; top: 15px; padding-bottom: 5px;">
        <i class="fas fa-comments fa-fw" aria-hidden="true"></i>
        <span>评论</span>
    </div>
    <div id="vcomments" class="card-content" style="display: grid">
    </div>
</div>

<script src="/libs/valine/av-min.js"></script>
<script src="/libs/valine/Valine.min.js"></script>
<script>
    new Valine({
        el: '#vcomments',
        appId: '1543373971',
        appKey: '111111',
        notify: 'false' === 'true',
        verify: 'false' === 'true',
        visitor: 'true' === 'true',
        avatar: 'mm',
        pageSize: '10',
        lang: 'zh-cn',
        placeholder: 'just go go'
    });
</script>

<!--酷Q推送-->


    

    
        <style>
    .mvaline-card {
        margin: 1.5rem auto;
    }

    .mvaline-card .card-content {
        padding: 20px 20px 5px 20px;
    }
</style>

<div class="card mvaline-card" data-aos="fade-up">
    <div class="comment_headling" style="font-size: 20px; font-weight: 700; position: relative; padding-left: 20px; top: 15px; padding-bottom: 5px;">
        <i class="fas fa-comments fa-fw" aria-hidden="true"></i>
        <span>评论</span>
    </div>
    <div id="mvcomments" class="card-content" style="display: grid">
    </div>
</div>

<script src="/libs/minivaline/MiniValine.js"></script>
<script>
    new MiniValine(Object.assign({"enable":true,"serverURL":"https://minivaline.your-domain.top"}, {
	  el: '#mvcomments',
    }));
</script>

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2022/08/10/java-mian-shi-ti-fen-bu-shi-wei-fu-wu-pian/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/23.jpg" class="responsive-img" alt="Java面试题（分布式微服务篇）">
                        
                        <span class="card-title">Java面试题（分布式微服务篇）</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            Java面试分布式与微服务题型相关讲解
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2022-08-10
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/" class="post-category">
                                    面试宝典
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Java/">
                        <span class="chip bg-color">Java</span>
                    </a>
                    
                    <a href="/tags/%E9%9D%A2%E8%AF%95/">
                        <span class="chip bg-color">面试</span>
                    </a>
                    
                    <a href="/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%B8%8E%E5%BE%AE%E6%9C%8D%E5%8A%A1/">
                        <span class="chip bg-color">分布式与微服务</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2022/08/09/java-mian-shi-ti-mysql-pian/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/23.jpg" class="responsive-img" alt="Java面试题（Mysql篇）">
                        
                        <span class="card-title">Java面试题（Mysql篇）</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            Java面试Mysql题型相关讲解
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2022-08-09
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-bookmark fa-fw icon-category"></i>
                            
                            <a href="/categories/%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/" class="post-category">
                                    面试宝典
                                </a>
                            
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/tags/Java/">
                        <span class="chip bg-color">Java</span>
                    </a>
                    
                    <a href="/tags/%E9%9D%A2%E8%AF%95/">
                        <span class="chip bg-color">面试</span>
                    </a>
                    
                    <a href="/tags/Mysql/">
                        <span class="chip bg-color">Mysql</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>


<script>
    $('#articleContent').on('copy', function (e) {
        // IE8 or earlier browser is 'undefined'
        if (typeof window.getSelection === 'undefined') return;

        var selection = window.getSelection();
        // if the selection is short let's not annoy our users.
        if (('' + selection).length < Number.parseInt('120')) {
            return;
        }

        // create a div outside of the visible area and fill it with the selected text.
        var bodyElement = document.getElementsByTagName('body')[0];
        var newdiv = document.createElement('div');
        newdiv.style.position = 'absolute';
        newdiv.style.left = '-99999px';
        bodyElement.appendChild(newdiv);
        newdiv.appendChild(selection.getRangeAt(0).cloneContents());

        // we need a <pre> tag workaround.
        // otherwise the text inside "pre" loses all the line breaks!
        if (selection.getRangeAt(0).commonAncestorContainer.nodeName === 'PRE' || selection.getRangeAt(0).commonAncestorContainer.nodeName === 'CODE') {
            newdiv.innerHTML = "<pre>" + newdiv.innerHTML + "</pre>";
        }

        var url = document.location.href;
        newdiv.innerHTML += '<br />'
            + '来源: Angus<br />'
            + '文章作者: YangChao<br />'
            + '文章链接: <a href="' + url + '">' + url + '</a><br />'
            + '本文章著作权归作者所有，任何形式的转载都请注明出处。';

        selection.selectAllChildren(newdiv);
        window.setTimeout(function () {bodyElement.removeChild(newdiv);}, 200);
    });
</script>


<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4'
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    

    <div class="container row center-align"
         style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2019-2023</span>
            
            <a href="/about" target="_blank">YangChao</a>
<!--             |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/Monkey-chao/Monkey-chao.github.io.git" target="_blank">Matery</a> -->
            <br>
            
                &nbsp;<i class="fas fa-chart-area"></i>&nbsp;站点总字数:&nbsp;<span
                        class="white-color">460.9k</span>
            
            
            
                
            
            
                <span id="busuanzi_container_site_pv">
                &nbsp;|&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;
                    <span id="busuanzi_value_site_pv" class="white-color"></span>
            </span>
            
            
                <span id="busuanzi_container_site_uv">
                &nbsp;|&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;
                    <span id="busuanzi_value_site_uv" class="white-color"></span>
            </span>
            
            <br>

            <!-- 运行天数提醒. -->
            
            <br>
            
        </div>
<!--         <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/Monkey-chao/Monkey-chao.github.io.git" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>




<!-- 

 -->


    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=1543373971" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 1543373971" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>


<!-- 




    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

 --></div>
 -->     </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    

    
        
        <script type="text/javascript">
            // 只在桌面版网页启用特效
            var windowWidth = $(window).width();
            if (windowWidth > 768) {
                document.write('<script type="text/javascript" src="/libs/others/sakura.js"><\/script>');
            }
        </script>
    

    <!-- 雪花特效 -->
    

    <!-- 鼠标星星特效 -->
    

    

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    

    

    

    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
